let Utils = {
    /**
     * 基本的数据类型判断
     */

    isNull: what => {
        return what === null || typeof (what) === 'undefined';
    },

    isArr: what => {
        return (!Utils.isNull(what) && what.constructor.toString().indexOf('Array') > -1);
    },

    isObj: what => {
        const type = typeof what;
        return what !== null && (type === 'object' || type === 'function');
    },

    isStr: what => {
        return typeof what === 'string' || what instanceof String;
    },

    isLink: what => {
        return Utils.isStr(what) && /^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+/.test(what);
    },

    isNum: what => {
        return !isNaN(parseFloat(what)) && isFinite(what);
    },

    isFn: what => {
        return (what && (typeof what === 'function' || what instanceof Function));
    },

    isBool: what => {
        return what === true || what === false;
    },

    /**
     * 是否是基础类型
     */
    isBasic: what => {
        return Utils.isStr(what) || Utils.isNum(what) || Utils.isBool(what) || Utils.isFn(what);
    },

    /**
     * 判断是否为空
     */
    isEmpty: what => {
        if (Utils.isArr(what)) {
            var i = 0,
                length = what.length;
            for (; i < length; i++) {
                // TODO 进一步判定类型
                if (Utils.isObjEmpty(what[i])) {
                    return true;
                }
            }
            return false;
        } else {
            return Utils.isObjEmpty(what);
        }
    },

    /**
     * 判断对象是否为空
     */
    isObjEmpty: what => {
        if (what) {
            var key;
            for (key in what) {
                return false;
            }
        }
        return true;
    },

    /**
     * 深拷贝
     * 注意：该方法会影响 a 的值
     */
    merge: (a, b) => {
        if (!a || !b) {
            return a || b;
        }

        Object.keys(b).forEach((bk) => {
            if (Utils.isNull(b[bk]) || Utils.isBasic(b[bk])) {
                a[bk] = b[bk];
            } else if (Utils.isArr(b[bk])) {
                a[bk] = [];
                b[bk].forEach((i) => {
                    if (Utils.isNull(i) || Utils.isBasic(i)) {
                        a[bk].push(i);
                    } else {
                        a[bk].push(Utils.merge(Utils.isArr(i) ? [] : {}, i));
                    }
                })
            } else if (b[bk].tagName && b[bk].appendChild && b[bk].removeChild && b[bk].style) {
                a[bk] = b[bk];
            } else {
                a[bk] = a[bk] || {};
                Utils.merge(a[bk], b[bk]);
            }
        });
        return a;
    },

    noop: () => {
        return undefined;
    },

    JSONCopy: obj => {
        return JSON.parse(JSON.stringify(obj));
    },

    // 更好做法：https://github.com/sindresorhus/object-assign
    assign: (a, b) => {
        let aa = Utils.merge({}, a);
        return Utils.merge(aa, b);
    },

    // from: https://github.com/lodash/lodash/blob/master/.internal/assignValue.js
    assignValue: (obj, key, value) => {
        const objValue = obj[key];

        if (!(Object.prototype.hasOwnProperty.call(obj, key) && Utils.eq(objValue, value)) || (value === undefined && !(key in object))) {
            obj[key] = value;
        }
    },

    // from: https://github.com/lodash/lodash/blob/master/eq.js
    eq: (value, other) => {
        return value === other || (value !== value && other !== other);
    },

    /**
     * 浅拷贝对象
     */
    extend: function (a, b) {
        var n;
        if (!b) {
            return a;
        }
        if (!a) {
            a = {};
        }
        for (n in b) {
            a[n] = b[n];
        }
        return a;
    },

    /**
     * 数值格式化，
     * @param val 需要格式化的数值
     * @param decimals 保留位数
     */
    numberFormat: (val, decimals, thousandsSep) => {

        val = (typeof decimals !== 'undefined' ? val.toFixed(decimals) : val).toString();
        // 分离数字的小数部分和整数部分
        var parts = val.split('.');

        // 整数部分加[thousandsSep]分隔, 借用一个著名的正则表达式
        parts[0] = parts[0].toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + (thousandsSep || ','));

        if (!decimals) {
            return parts[0];
        }

        return parts.join('.');
    },

    /**
     * 时间格式函数
     * @param {number} timestamp 时间戳，为空则取当前时间
     * @param {string} separator 年月日之间的分隔符
     * @param {boolean} isDateTime 是否包含时分秒
     */
    dateFormat: (timestamp, separator, isDateTime) => {
        if (separator === undefined) {
            separator = '';
        }
        var date = timestamp ? new Date(timestamp) : new Date(),
            year = date.getFullYear(),
            month = date.getMonth() + 1,
            day = date.getDate(),
            result = year + separator + (month > 9 ? month : '0' + month) + separator + (day > 9 ? day : '0' + day);

        if (!isDateTime) {
            return result;
        }

        var hours = date.getHours(),
            minutes = date.getMinutes(),
            seconds = date.getSeconds();

        return result + ' ' + Utils.timeFormat(null, date);
    },

    timeFormat: (timestamp, date) => {
        if (!date) {
            date = timestamp ? new Date(timestamp) : new Date();
        }

        let hours = date.getHours(),
            minutes = date.getMinutes(),
            seconds = date.getSeconds();

        return (hours > 9 ? hours : '0' + hours) + ':' + (minutes > 9 ? minutes : '0' + minutes) + ':' + (seconds > 9 ? seconds : '0' + seconds);
    },

    shortTimeFormat: timestamp => {
        let date = timestamp ? new Date(timestamp) : new Date(),
            hours = date.getHours(),
            minutes = date.getMinutes();

        return (hours > 9 ? hours : '0' + hours) + ':' + (minutes > 9 ? minutes : '0' + minutes);
    },

    /**
     * 
     */
    isPhone: () => {
        var check = false;
        (function (a) {
            if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true;
        })(navigator.userAgent || navigator.vendor || window.opera);
        return check;
    },

    /**
     * 时间单位
     */
    timeUnit: {
        seconds: 1000,
        minutes: 60 * 1000,
        hours: 60 * 60 * 1000,
        day: 24 * 60 * 60 * 1000,
        month: 30 * 24 * 60 * 60 * 1000
    },

    /**
     * 去除无用的属性，主要是用于处理主数据
     * @param {*} obj 
     */
    deleteNeedlessProp(obj) {
        if (!obj) {
            return false;
        }
        let keys = ['createdBy', 'createdDt', 'updatedBy', 'updatedDt'];

        keys.forEach(k => {
            delete obj[k];
        });
        return obj;
    },



    /**
     * 时间取整
     * @param {number} timestamp 时间戳
     * @param {number} unit 时间单位（时间戳）
     * @param {boolean} isCeil 
     */
    timeRound(timestamp, unit, isCeil) {
        let count = Math[isCeil ? 'ceil' : 'floor'](timestamp / unit);
        return count * unit;
    },

    /**
     * 计算时间范围
     * 算法根据老代码提取，如果有疑问，请联系后端开发人员
     * @param {timestamp | null} firstTime 开始时间
     * @param {Int | null} x 当前画布中心位置
     */
    calcTime(firstTime, x) {

        let screenWidth = window.innerWidth,
            cacheFactor = 2,
            width = screenWidth * cacheFactor,
            _spacing = 150,
            _scale = 1,
            factor = 1 / 3,
            _scaledSpacing = _spacing * _scale,
            amount = Math.ceil(width / _scaledSpacing),
            millis = null;

        // console.log(screenWidth, amount);

        if (firstTime) {
            let _scaledMillisPerPixel = 1000 * 60 * 60 / _scaledSpacing,
                post = x - 1,
                offset = _scaledMillisPerPixel * post;

            millis = firstTime + offset - 60 * 60 * 1000 * amount / 2;
        }

        if (!millis) {
            millis = new Date().getTime() - amount * 60 * 60 * 1000 * factor;
        }

        millis = Utils.timeRound(millis, Utils.timeUnit.hours);

        return [millis, millis + amount * Utils.timeUnit.hours];
    },

    // 计算两个时间范围的交集
    calcTimeMixed(a, b) {

        /// 
        if (b[0] > a[1] || b[1] < a[0]) {
            return false;
        }
        let range = [a[0], a[1]];

        if (b[0] > range[0]) {
            range[0] = b[0];
        }
        if (b[1] < range[1]) {
            range[1] = b[1];
        }
        return range;
    },

    // 全局变量
    lanes: null,
    facilities: null,
    facilitiesNoMap: null,
    /**
     * 根据设备信息计算泳道数据，计算方法是：
     *  同一类设备一个泳道（颜色相同，对应的在图表中用 PlotBand 表示），计算后每个泳道包含的信息：
     *  {
     *      name: 泳道名称，对应的是设备编号,
     *      min: 泳道起始值,
     *      max: 泳道结束值，如果没有值，则直接 min + 1,
     *      color: 泳道颜色
     *  }
     *  计算完毕后将泳道数据缓存在 Utils 中
     * @param {List} facilities 
     * @returns 设备编号下标对象，形如 { BOF4: 0, BOF5: 1}，是为方便图表中设备编号与 Y 值的映射
     */
    calcLane(facilities) {
        let lastProccessNo = null,
            laneColor = ['#FFFFE0', '#E0FFFF', '#FFE0FF', '#E0E0FF', '#E0FFD0', '#FFEAD0'],
            colorLength = laneColor.length,
            facilitiesNoMap = {},
            lanes = [];

        facilities.forEach((f, i) => {
            facilitiesNoMap[f.facilityNo] = i;
            if (lastProccessNo !== f.proccessNo) {
                lanes.push({
                    name: f.proccessNo,
                    min: i,
                    color: laneColor[lanes.length % colorLength],
                    max: i + 1
                });
            } else {
                lanes[lanes.length - 1].max = i + 1;
            }
            lastProccessNo = f.proccessNo;
        });
        Utils.lanes = lanes;
        Utils.facilities = facilities;
        Utils.facilitiesNoMap = facilitiesNoMap;
        return facilitiesNoMap;
    },

    /**
     * 计算泳道内的点是否可拖动
     * @param {Number} lane 泳道值（0.5, 1 这样的值，表示当前点在泳道中值）
     * @return {object} 泳道值
     */
    calcLaneDrag(lane) {
        let i = 0,
            total = Utils.lanes.length;
        for (; i < total; i++) {
            if (lane > Utils.lanes[i].min && lane <= Utils.lanes[i].max) {
                return Utils.lanes[i];
            }
        }
        return null;
    },

    /**
     * 根据上面计算的泳道数据生成图表所需的标识区域数据
     */
    calcPlotbands() {
        let plotBands = [];
        Utils.lanes.forEach(lan => {
            plotBands.push({
                from: lan.min,
                to: lan.max || (lan.min + 1),
                color: lan.color
            })
        });
        return plotBands;
    },

    /**
     * 时间字符串填充，填充为完整的 yyyy-mm-dd HH:MM:SS
     * @param {string} timeStr 时间字符串
     */
    dateFill(timeStr) {
        timeStr = timeStr.replace('T', ' ');
        let length = timeStr.length;
        if (length < 19) {
            switch (length) {
                case 10:
                    return timeStr + ' 00:00:00';
                case 13:
                    return timeStr + ':00:00';
                case 16:
                    return timeStr + ':00';
                default:
                    console.log(timeStr + 'is invalid');
                    return timeStr;
            }
        }
        return timeStr;
    }


}

export default Utils;